package no.ntnu.fp.su;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;

import no.ntnu.fp.db.Database;

/**
 * Master of WatchWells
 * 
 * @author stn
 * 
 */
public class Platform extends Thread {

	private ArrayList<WatchWell> wells;
	private int platID;
	private Events events;
	private Database db;

	private boolean isRunning;

	private Status status;

	private ControlSystem cs;
	private boolean loaded;
	private boolean isWatch;
	private PropertyChangeSupport pcs;

	/**
	 * Tests if this Platform is a launched by normal Watch (and therefore
	 * actually runs any associated {@link WatchWell})
	 * 
	 * @return False if this Platform is launched through a {@link Master}
	 * @see Watch#isWatch()
	 */
	public boolean isWatch() {
		return isWatch;
	}

	/**
	 * Constructor for {@link Platform}
	 * 
	 * @param {@link Watch} - Watch that runs this platform
	 * @param int PlatformID - The Id of this platform
	 * @param String
	 *            Name - The name of this platform
	 */
	public Platform(int id, String name) {
		System.out.println("Platform constructor, WATCH");
		wells = new ArrayList<WatchWell>();
		platID = id;
		isWatch = true;
		cs = new ControlSystem();
		db = new Database();
		events = new Events(id, db);
		loaded = false;
		loadWells();

		setName(name);
		pcs = new PropertyChangeSupport(this);
	}

	/**
	 * Constructor for Platform
	 * 
	 * @param int id - The unique id for this Platform
	 * @param {@link String} name - This Platforms name
	 * @param boolean isWatch - Is this platform launched by a {@link Watch} (as
	 *        opposed to a {@link Master})
	 */
	public Platform(int id, String name, boolean isWatch) {
		System.out.println("Platform constructor, !MASTER/WATCH " + isWatch);
		wells = new ArrayList<WatchWell>();
		platID = id;
		this.isWatch = isWatch;
		cs = new ControlSystem();
		db = new Database();
		events = new Events(id, db);
		events.getEventsFromDB();
		loaded = false;
		loadWells();

		setName(name);
		pcs = new PropertyChangeSupport(this);
	}

	/**
	 * Starter alle brønner (som ikke allerede kjører) på platformen
	 * 
	 * @param startPoll
	 *            - skal brønnene legges til i controlsystemet også
	 */
	public void startWells(boolean startPoll) {
		System.out.println("Starting wells");
		for (WatchWell w : wells) {
			if (w.getState() == Thread.State.NEW)
				w.start();
			if (startPoll) {
				w.startPoll();
			}
		}

	}

	/**
	 * Gets the Id of this Platform
	 * 
	 * @return int id - the unique Platform id
	 */
	public int getID() {
		return platID;
	}

	/**
	 * Hoved tråd for {@link Platform} tråd. Startes via start(). Vil bare vente
	 * på data fra {@link WatchWell}
	 */
	public void run() {
		isRunning = true;
		System.out.println(this.getName() + " started");

		events.getEventsFromDB();

		System.out.println(isWatch);
		startWells(true);

		while (isRunning) {
			try {
				// System.out.println("Platform sleeping");
				sleep(500);
			} catch (InterruptedException e) {
				// System.out.println("Platform interrupted");
				boolean change = false;
				Status oldstatus = status;
				synchronized (this) {
					status = Status.GREEN;
					for (WatchWell tmpWell : wells) {
						synchronized (tmpWell) {
							if (tmpWell.getStatusChanged()) {
								change = true;
								if (status == Status.GREEN
										&& tmpWell.getStatus() != Status.GREEN) {
									status = tmpWell.getStatus();
									if (status == Status.RED)
										break;
								} else if (status == Status.YELLOW
										&& tmpWell.getStatus() == Status.RED) {
									status = Status.RED;
									break;
								}

								// tmpWell.clearStatusChanged();
								// if (tmpWell.getStatus() != Status.GREEN &&
								// status != Status.RED)
								// status = tmpWell.getStatus();
							}
						}
					}
					if (change) {
						pcs.firePropertyChange("PStatus" + platID, oldstatus,
								status);

					}
				}
			}
		}
	}

	public void addPropertyChangeListener(PropertyChangeListener p) {
		if (p == null)
			return;
		for (PropertyChangeListener pcl : pcs.getPropertyChangeListeners()) {
			if (pcl == p)
				return;
		}
		pcs.addPropertyChangeListener(p);
	}

	/**
	 * Stopper platformen, stopper brønnene på denne platformen, og fjerner dem
	 * fra {@link ControlSystem}
	 * 
	 */
	public synchronized void stopRunning() {

		for (WatchWell ww : wells) {
			removeWell(ww);
		}
		cs.getTimer().cancel();
		do {
			isRunning = false;
		} while (isRunning);

	}

	/**
	 * Gets the associated {@link ControlSystem}
	 * 
	 * @return {@link ControlSystem}
	 */
	public ControlSystem getControlSystem() {
		return cs;
	}

	/**
	 * Gets the {@link Events} on this Platform
	 * 
	 * @return {@link Events}
	 */
	public Events getEvents() {
		return events;
	}

	/**
	 * Gets the {@link Status} of this Platform
	 * 
	 * @return {@link Status}
	 */
	public Status getStatus() {
		return status;
	}

	/**
	 * Legger til en brønn
	 */
	public void addWell(int id) {
		System.out.println("Creating Watchwell");
		WatchWell w = new WatchWell(this, id);
		wells.add(w);
		System.out.println("added " + w.getName());
		// w.start();
	}

	/**
	 * create a new well, that will be written to the db
	 * 
	 * @param critR
	 * @param critP
	 * @param critT
	 * @param evLimit
	 * @param dTlimit
	 * @param dPlimit1
	 * @param dPlimit2
	 * @param pLimit
	 */
	public WatchWell newWell(double critR, double critP, double critT,
			double evLimit, double dTlimit, double dPlimit1, double dPlimit2,
			double pLimit) {
		if (!loaded)
			throw new NullPointerException("Wells not loaded from db");
		int wellId = newWellID();

		WatchWell w = new WatchWell(this, wellId, critR, critP, critT, evLimit,
				dTlimit, dPlimit1, dPlimit2, pLimit);
		wells.add(w);
		db.connect();
		db.execute("insert into platformwell values(" + platID + "," + wellId
				+ ")");
		db.execute("insert into welldata values(" + wellId + "," + critR + ","
				+ critP + "," + critT + "," + evLimit + "," + dTlimit + ","
				+ dPlimit1 + "," + dPlimit2 + "," + pLimit + ")");
		db.disconnect();

		w.start();
		return w;

	}

	/**
	 * get next free id
	 * 
	 * @return the next free id
	 */
	private synchronized int newWellID() {
		boolean free;
		db.connect();
		ResultSet rs = db.query("select wellid from platformwell");
		ArrayList<Integer> wellids = new ArrayList<Integer>();
		try {
			while (rs.next()) {
				wellids.add(rs.getInt(1));
			}
		} catch (SQLException e) {
			System.out.println("error loading existing wells from db");
			e.printStackTrace();
		}
		for (int i = 0; i < 10000; i++) {
			free = true;
			for (int j : wellids) {
				if (j == i) {
					free = false;
					break;
				}
			}
			if (free)
				return i;
		}
		return 0;
	}

	/**
	 * Skaffer brønn med bestemt id
	 * 
	 * @param id
	 *            til brønn
	 * @return brønn med valgt id, eller null dersom denne brønnen ikke er på
	 *         denne platformen
	 */
	public WatchWell getWell(int id) {
		// return i >= wells.size() || i < 0 ? null : wells.get(i);
		for (WatchWell we : wells) {
			if (we.getWellID() == id)
				return we;
		}
		return null;
	}

	/**
	 * Gets a {@link ArrayList} of all {@link WatchWell} on this Platform
	 * 
	 * @return {@link ArrayList} of {@link WatchWell}
	 */
	public ArrayList<WatchWell> getWells() {
		return wells;
	}

	/**
	 * Fjerner den spesifiserte brønnen fra platformen, fjerner den også fra
	 * {@link ControlSystem}et også dersom den er der.
	 * 
	 * @param {@link WatchWell}
	 */
	public void removeWell(WatchWell w) {
		wells.remove(w);
		w.stopRunning();
	}

	/**
	 * Laster inn brønner fra 'welldata' databasen
	 */
	public void loadWells() {
		if (loaded)
			return;
		Database db = new Database();
		ResultSet rs;

		int wellId;
		double critR;
		double critP;
		double critT;
		double evLimit;
		double dTlimit;
		double dPlimit1;
		double dPlimit2;
		double pLimit;

		db.connect();
		rs = db
				.query("SELECT w.* FROM welldata w,platformwell p WHERE w.wellid = p.wellid and p.platformid = "
						+ platID);
		try {
			while (rs.next()) {
				wellId = rs.getInt(1);
				critR = rs.getDouble(2);
				critP = rs.getDouble(3);
				critT = rs.getDouble(4);
				evLimit = rs.getDouble(5);
				dTlimit = rs.getDouble(6);
				dPlimit1 = rs.getDouble(7);
				dPlimit2 = rs.getDouble(8);
				pLimit = rs.getDouble(9);

				wells.add(new WatchWell(this, wellId, critR, critP, critT,
						evLimit, dTlimit, dPlimit1, dPlimit2, pLimit));
			}
			loaded = true;
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// les fra db basert p� platID

	}

	/**
	 * Gir navn og Id til denne platformen
	 * 
	 * @return {@link String}
	 * 
	 */
	public String toString() {
		return getName() + " " + platID;
	}

	/**
	 * FOR DEBUG ONLY
	 * 
	 * @param args
	 * @deprecated
	 */
	public static void main(String[] args) {

		System.out.println("Creating Platform p");
		Platform p = new Platform(1, null);
		System.out.println(p.getName() + " created");

		System.out.println("Adding wells from db");
		// p.loadWells();

		// System.out.println("Adding Well 1");
		// p.addWell();
		// System.out.println(p.getWell(0).getName() + " created");
		// System.out.println("Adding Well 2");
		// p.addWell();
		// System.out.println(p.getWell(1).getName() + " created");

		System.out.println("Starting threads in 3 sec");
		try {
			sleep(3000);
		} catch (InterruptedException e1) {
			// TODO Auto-generated catch block
			System.out.println("Interrupted !?");
			e1.printStackTrace();
		}
		p.start();
		p.startWells(true);
		p.startWells(true); // bare for å sjekke at dem ikke starter igjen

		System.out.println("Main done");

		// System.out.println("Adding wells to cs in 2 sec");
		// try {
		// sleep(2000);
		// } catch (InterruptedException e1) {
		// // TODO Auto-generated catch block
		// System.out.println("Interrupted !?");
		// e1.printStackTrace();
		// }
		// p.getWell(0).startPoll();
		// p.getWell(1).startPoll();

		// while(true)) {
		// try {
		// sleep(5000);
		// } catch (InterruptedException e) {
		// // TODO Auto-generated catch block
		// System.out.println("Main thread interrupted");
		// e.printStackTrace();
		// }
		// // System.out.println("Main sleeping");
		// }

	}

	/**
	 * Adds an {@link Event}
	 * 
	 * @param {@link String} - Event content
	 * @return {@link Event}
	 */
	public Event addEvent(String content) {
		System.out.println("Creating Event");
		int id = newEventID();
		Event e = new Event(id, new Timestamp(System.currentTimeMillis()),
				content);
		events.addEvent(e);
		System.out.println("added " + e.getContent());
		pcs.firePropertyChange("newEvent", null, e);
		return e;
		// w.start();
	}

	/**
	 * Gets the next free EventId
	 * 
	 * @return the next free EventId
	 */
	private synchronized int newEventID() {
		boolean free;
		db.connect();
		ResultSet rs = db.query("select eventid from events");
		ArrayList<Integer> eventids = new ArrayList<Integer>();
		try {
			while (rs.next()) {
				eventids.add(rs.getInt(1));
			}
		} catch (SQLException e) {
			System.out.println("error loading existing events from db");
			e.printStackTrace();
		}
		for (int i = 0; i < 10000; i++) {
			free = true;
			for (int j : eventids) {
				if (j == i) {
					free = false;
					break;
				}
			}
			if (free)
				return i;
		}
		return 0;
	}

}
